﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;

namespace IntoNails.Infrastructure.ViewModels
{
    public delegate void NodeHandler(TreeNodeViewModel currentNode);

    public enum NodeStatus
    {
        Selected,
        Added,
        Edited,
        Deleted
    }

    public class TreeNodeViewModel : NotificationObject, ITreeNodeViewModel
    {
        public event NodeHandler Expanding;
        public event EventHandler Expanded;

        public event NodeHandler Collapsing;
        public event EventHandler Collapsed;

        public event NodeHandler Selected;

        #region Fields

        private string _displayName;
        private object _tag;
        private object _associatedObject;

        private string _normalIcon;
        private string _expandIcon;

        private TreeNodeViewModel _parent;
        private IList<ITreeNodeViewModel> _children;

        private bool _isChecked;
        private bool _isReadonly;
        private bool _isSelected;
        private bool _isExpanded;

        private int _level;
        
        #endregion

        #region Constructors

        public TreeNodeViewModel(string name)
            : this(name, null, null, null)
        {
        }

        public TreeNodeViewModel(string name, TreeNodeViewModel parent, object associatedObject, object tag)
            : this(name, parent, string.Empty, string.Empty, associatedObject, tag)
        {
        }

        public TreeNodeViewModel(string name, TreeNodeViewModel parent, string normalIcon, string expandedIcon, object associatedObject, object tag)
        {
            this.DisplayName = name;
            this.Parent = parent;
            this.Children = new List<ITreeNodeViewModel>();
            this.Level = parent != null ? parent.Level + 1 : 0;
            this.NormalIcon = normalIcon;
            this.ExpandedIcon = expandedIcon;
            this.AssociatedObject = associatedObject;
            this.Tag = tag;

            this.MouseClickCommand = new DelegateCommand(this.OnMouseClickExecuted);
            this.AddCommand = new DelegateCommand(this.ExecuteAddNewNode);
            this.EditCommand = new DelegateCommand(this.ExecuteEdit, this.CanExecuteEdit);
            this.DeleteCommand = new DelegateCommand(this.ExecuteDelete, this.CanExecuteDelete);
        }

        #endregion

        #region Properties

        public string DisplayName
        {
            get { return this._displayName; }
            set
            {
                this._displayName = value;
                base.RaisePropertyChanged(() => DisplayName);
            }
        }

        public object AssociatedObject
        {
            get { return this._associatedObject; }
            set
            {
                this._associatedObject = value;
                base.RaisePropertyChanged(() => AssociatedObject);
            }
        }

        public object Tag
        {
            get { return this._tag; }
            set
            {
                this._tag = value;
                base.RaisePropertyChanged(() => Tag);
            }
        }

        public string NormalIcon
        {
            get { return this._normalIcon; }
            set
            {
                this._normalIcon = value;
                base.RaisePropertyChanged(() => NormalIcon);
            }
        }

        public string ExpandedIcon
        {
            get { return this._expandIcon; }
            set
            {
                this._expandIcon = value;
                base.RaisePropertyChanged(() => NormalIcon);
            }
        }

        public TreeNodeViewModel Parent
        {
            get { return this._parent; }
            set
            {
                this._parent = value;
                base.RaisePropertyChanged(() => Parent);
            }
        }

        public IList<ITreeNodeViewModel> Children
        {
            get { return this._children; }
            set
            {
                this._children = value;
                base.RaisePropertyChanged(() => Children);
            }
        }

        public bool IsReadonly
        {
            get { return this._isReadonly; }
            set
            {
                this._isReadonly = value;
                base.RaisePropertyChanged(() => IsReadonly);
            }
        }

        public bool IsSelected
        {
            get { return this._isSelected; }
            set
            {
                this._isSelected = value;

                if (value)
                    this.OnSelected();

                base.RaisePropertyChanged(() => IsSelected);
            }
        }

        public bool IsExpanded
        {
            get { return this._isExpanded; }
            set
            {
                this.OnExpanding();
                this._isExpanded = value;
                this.OnExpanded(EventArgs.Empty);
                base.RaisePropertyChanged(() => IsExpanded);
            }
        }

        public bool IsChecked
        {
            get { return this._isChecked; }
            set
            {
                this._isChecked = value;
                base.RaisePropertyChanged(() => IsChecked);
            }
        }

        public int Level
        {
            get { return this._level; }
            set
            {
                this._level = value;
                base.RaisePropertyChanged(() => Level);
            }
        }

        public bool IsRoot
        {
            get { return this.Level == 0; }
        }

        public bool IsLeaf
        {
            get { return this.Children.Count == 0; }
        }

        public NodeStatus Status { get; private set; }

        #endregion

        public ITreeNodeViewModel GetAncestor(Func<ITreeNodeViewModel, bool> predicate)
        {
            ITreeNodeViewModel node = this;
            while (node.Parent != null)
            {
                node = node.Parent;

                if (predicate(node))
                {
                    return node;
                }
            }

            return null;
        }

        public IEnumerable<ITreeNodeViewModel> GetAncestors()
        {
            var parent = this.Parent;
            while (parent != null)
            {
                yield return parent;
                parent = parent.Parent;
            }
        }

        public IEnumerable<ITreeNodeViewModel> GetDescendants()
        {
            Stack<ITreeNodeViewModel> nodes = new Stack<ITreeNodeViewModel>(new[] {this});
            while (nodes.Any())
            {
                ITreeNodeViewModel node = nodes.Pop();
                yield return node;

                foreach (var child in node.Children)
                {
                    nodes.Push(child);
                }
            }
        }
        
        #region Commands

        public ICommand MouseClickCommand { get; private set; }
        public ICommand AddCommand { get; private set; }
        public ICommand EditCommand { get; private set; }
        public ICommand DeleteCommand { get; private set; }

        #endregion

        protected virtual void OnExpanding()
        {
            var expandingHandler = this.Expanding;
            if (expandingHandler != null)
                expandingHandler(this);
        }

        protected virtual void OnExpanded(EventArgs e)
        {
            var expandedHandler = this.Expanded;
            if (expandedHandler != null)
                expandedHandler(this, e);
        }

        protected virtual void OnCollapsing(CancelEventArgs e)
        {
            var collapsingHandler = this.Collapsing;
            if (collapsingHandler != null)
                collapsingHandler(this);
        }

        protected virtual void OnCollapsed(EventArgs e)
        {
            var collapsedHandler = this.Collapsed;
            if (collapsedHandler != null)
                collapsedHandler(this, e);
        }

        protected virtual void OnSelected()
        {
            var handler = this.Selected;
            if (handler != null)
                handler(this);
        }
        
        #region Command Action Handlers

        private void OnMouseClickExecuted()
        {
            this.Status = NodeStatus.Selected;
            this.OnSelected();
        }

        private void ExecuteAddNewNode()
        {
            this.Status = NodeStatus.Added;
            this.OnSelected();
        }

        private void ExecuteEdit()
        {
            this.Status = NodeStatus.Edited;
            this.OnSelected();
        }

        private void ExecuteDelete()
        {
            this.Status = NodeStatus.Deleted;
            this.OnSelected();
        }

        #endregion

        #region Command Predicate Handlers

        private bool CanExecuteEdit()
        {
            return !this.IsReadonly;
        }

        private bool CanExecuteDelete()
        {
            return !this.IsReadonly;
        }

        #endregion

        public override string ToString()
        {
            return this.DisplayName;
        }
    }
}
